﻿using System;
using System.Linq;
using System.Collections.Generic;

namespace xWF
{
    public abstract class Workflow<T>
    {
        public T WorkItem { get; protected set; }

        public abstract IWorkflowState<T> CurrentState { get; protected set;}

        /// <summary>
        /// Выполнить переход
        /// </summary>
        /// <param name="transition"></param>
        protected void Execute(IWorkflowTransition<T> transition)
        {
            if (transition == null)
            {
                throw new ArgumentNullException("transition");
            }

            var currentState = CurrentState;

            if (currentState == null)
            {
                throw new InvalidOperationException("Текущее состояние процесса неопределено");
            }

            if (!allStateTransitions.Contains(transition) && !states[currentState].Contains(transition))
            {
                throw new InvalidOperationException("Недопустимый переход из текущего состояния");
            }

            var newState = transition.Execute(WorkItem);

            if (newState == null || newState == currentState)
            {
                // Остаемся в текущем состоянии
                return;
            }

            if (!states.ContainsKey(newState))
            {
                throw new InvalidOperationException("Переход в недопустимое состояние");
            }
			
            GoToState(newState);
        }

        /// <summary>
        /// Допустимые переходы из текущего состояния
        /// </summary>
        /// <returns></returns>
        protected IEnumerable<IWorkflowTransition<T>> AvailableTransitions()
        {
            if (!states.ContainsKey(CurrentState))
            {
                return new IWorkflowTransition<T>[0];
            }

            return states[CurrentState].Concat(allStateTransitions).ToArray();
        }

        /// <summary>
        /// Добавить состояние и переходы из него
        /// </summary>
        /// <param name="state">Состояние</param>
        /// <param name="transitions">Допустимые переходы данного состояния</param>
        protected void AddState(IWorkflowState<T> state, params IWorkflowTransition<T>[] transitions)
        {
            if (!states.ContainsKey(state))
            {
                states.Add(state, new List<IWorkflowTransition<T>>());
            }

            states[state].AddRange(transitions);
        }

        /// <summary>
        /// Добавление перехода, доступного во всех состояниях
        /// </summary>
        /// <param name="transitions"></param>
        protected void AddAllStateTransitions(params IWorkflowTransition<T>[] transitions)
        {
            allStateTransitions.AddRange(transitions);
        }

        /// <summary>
        /// Удалить состояние
        /// </summary>
        /// <param name="state"></param>
        protected void RemoveState(IWorkflowState<T> state)
        {
            if (states.ContainsKey(state))
            {
                states.Remove(state);
            }
        }

        private void GoToState(IWorkflowState<T> state)
        {
            CurrentState.OnExit(WorkItem);
            state.OnEnter(WorkItem);
            CurrentState = state;
        }

        private readonly Dictionary<IWorkflowState<T>, List<IWorkflowTransition<T>>> states = new Dictionary<IWorkflowState<T>, List<IWorkflowTransition<T>>>();

        private readonly List<IWorkflowTransition<T>> allStateTransitions = new List<IWorkflowTransition<T>>();
    }
}